/*******************************************************************************
* Copyright 2012 - Joakim Erdfelt
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*******************************************************************************/
package net.erdfelt.android.sdkfido.configer;
import java.io.File;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.Writer;
import java.lang.reflect.Field;
import java.util.Arrays;
import java.util.LinkedList;
import java.util.List;
import java.util.ListIterator;
import org.apache.commons.beanutils.ConvertUtils;
import org.apache.commons.lang.StringUtils;
public class ConfigCmdLineParser {
public static final String OPT_HELP = "help";
public static final String OPT_SAVE = "save-config";
public static final String OPT_CONFIG = "config";
private Configer configer;
private Class<?> mainclass;
private PrintWriter out;
public ConfigCmdLineParser(Object mainclassobj, Object obj) {
this(mainclassobj.getClass(), obj);
}
public ConfigCmdLineParser(Class<?> mainclass, Object obj) {
this.mainclass = mainclass;
this.configer = new Configer(obj);
this.configer.addManualConfigurable(OPT_HELP, null, "show this help screen");
this.configer.addManualConfigurable(OPT_SAVE, null, "save configuration options to disk");
this.configer.addManualConfigurable(OPT_CONFIG, "File", "the filename to persist configuration options in");
this.out = new PrintWriter(System.out);
}
public void parse(String[] args) throws CmdLineParseException {
LinkedList<String> arglist = new LinkedList<String>();
arglist.addAll(Arrays.asList(args));
// Quick Help
if (arglist.contains("--" + OPT_HELP)) {
usage();
return;
}
// Configuration File Option
int idx = arglist.indexOf("--" + OPT_CONFIG);
if (idx >= 0) {
if (idx + 1 > arglist.size()) {
throw new CmdLineParseException("Expected <File> parameter for option: --" + OPT_CONFIG);
}
String value = arglist.get(idx + 1);
File file = (File) ConvertUtils.convert(value, File.class);
this.configer.setPersistFile(file);
arglist.remove(idx + 1);
arglist.remove(idx);
}
// Save Options Option
boolean saveOptions = false;
idx = arglist.indexOf("--" + OPT_SAVE);
if (idx >= 0) {
saveOptions = true;
arglist.remove(idx);
}
// Restore from persist first.
try {
configer.restore();
} catch (IOException e) {
throw new CmdLineParseException("Unable to load configuration: " + e.getMessage(), e);
}
// Set values from command line now.
String value;
ListIterator<String> iter = arglist.listIterator();
while (iter.hasNext()) {
String arg = iter.next();
if (arg.startsWith("--")) {
// Its an option.
String optname = arg.substring(2);
Configurable cfgrbl = configer.getConfigurable(optname);
if (cfgrbl == null) {
throw new CmdLineParseException("Invalid Option: " + arg);
}
if (!iter.hasNext()) {
throw new CmdLineParseException("Expected <" + cfgrbl.getType() + "> parameter for option: " + arg);
}
value = iter.next();
configer.setValue(optname, value);
continue; // process next arg
}
// All others are considered args.
addToRawArgs(arg);
}
// Save options (if specified)
if (saveOptions) {
try {
configer.persist();
} catch (IOException e) {
throw new CmdLineParseException("Unable to save configuration: " + e.getMessage(), e);
}
}
}
private void addToRawArgs(String arg) throws CmdLineParseException {
if (!configer.hasRawArgsDefined()) {
throw new CmdLineParseException("No @ConfigArguments annotation setup for raw arguments.");
}
configer.addRawArg(arg);
}
public void setOut(PrintWriter out) {
this.out = out;
}
public void setOut(Writer writer) {
if (writer instanceof PrintWriter) {
this.out = (PrintWriter) writer;
} else {
this.out = new PrintWriter(writer);
}
}
public PrintWriter getOut() {
return out;
}
private String toOptionExample(Configurable cfgr) {
StringBuilder opt = new StringBuilder();
opt.append("--").append(cfgr.getKey());
if (cfgr.hasType()) {
opt.append(" <").append(cfgr.getType()).append(">");
}
return opt.toString();
}
private String toSectionHeader(String message) {
return StringUtils.rightPad(String.format(".\\ %s \\.", message), 78, '_');
}
public void usage() {
int maxOptSize = 0;
for (Configurable cfgr : configer.getConfigurables()) {
maxOptSize = Math.max(maxOptSize, toOptionExample(cfgr).length());
}
String argname = configer.getRawArgsName();
out.printf("Usage: java -jar " + mainclass.getName() + " [options...] [%s...]%n", argname);
List<String> scopes = configer.getScopeIds();
for (String scope : scopes) {
out.println();
out.println(toSectionHeader(configer.getScopeDescription(scope)));
for (Configurable cfgr : configer.getConfigurables()) {
if (StringUtils.equals(cfgr.getScope(), scope)) {
usage(cfgr, maxOptSize);
}
}
}
out.flush();
}
private void usage(Configurable cfgr, int maxOptSize) {
out.print(StringUtils.rightPad(toOptionExample(cfgr), maxOptSize, ' '));
out.printf(" : %s%n", cfgr.getDescription());
String indent = StringUtils.rightPad("", maxOptSize + 3);
if (cfgr.getField() == null) {
// Manually Added Configurable.
if (OPT_CONFIG.equals(cfgr.getKey())) {
out.printf("%s(default value: %s)%n", indent, configer.getPersistFile().getAbsolutePath());
}
out.flush();
return;
}
if (cfgr.getField().getType().isEnum()) {
for (Field f : cfgr.getField().getType().getDeclaredFields()) {
if (f.isEnumConstant()) {
out.printf("%s\"%s\"", indent, f.getName());
ConfigOption copt = f.getAnnotation(ConfigOption.class);
if (copt != null) {
out.printf(" - %s", copt.description());
}
out.println();
}
}
}
Object obj = configer.getValue(cfgr.getName());
if (obj != null) {
out.printf("%s(default value: %s)%n", indent, obj);
}
out.flush();
}
/**
* Variant of {@link #usage()} but prints the failure as the error message, then the usage, then the stacktrace
* information.
*
* @param e
*/
public void usage(CmdLineParseException e) {
out.println("ERROR: " + e.getMessage());
usage();
e.printStackTrace(out);
out.flush();
}
}